/*
 * 
 */
package fr.univtln.superplanning.persistence;

import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_DRIVER;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_PASSWORD;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_URL;
import static org.eclipse.persistence.config.PersistenceUnitProperties.JDBC_USER;
import static org.eclipse.persistence.config.PersistenceUnitProperties.LOGGING_LEVEL;
import static org.eclipse.persistence.config.PersistenceUnitProperties.LOGGING_SESSION;
import static org.eclipse.persistence.config.PersistenceUnitProperties.LOGGING_THREAD;
import static org.eclipse.persistence.config.PersistenceUnitProperties.LOGGING_TIMESTAMP;
import static org.eclipse.persistence.config.PersistenceUnitProperties.TARGET_SERVER;
import static org.eclipse.persistence.config.PersistenceUnitProperties.TRANSACTION_TYPE;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.spi.PersistenceUnitTransactionType;

import org.eclipse.persistence.config.TargetServer;

import fr.univtln.superplanning.exceptions.NegativeNumberException;

// TODO: Auto-generated Javadoc
/**
 * This class provides ways to updates the connections properties to connect on
 * the database.
 * 
 * @author vincent
 * 
 */
public class DBLogin extends Observable {

	/** The Constant logger. */
	private static final Logger logger = Logger.getLogger("superplanning_logger");

	/** The protocole. */
	private String protocole;

	/** The host. */
	private String host;

	/** The port. */
	private int port;

	/** The database name. */
	private String databaseName;

	/** The user name. */
	private String userName;

	/** The password. */
	private String password;

	// Some defaults values
	/** The logging level. */
	private String loggingLevel = "FINE";

	/** The logging timestamp. */
	private boolean loggingTimestamp = false;

	/** The logging thread. */
	private boolean loggingThread = false;

	/** The loggin session. */
	private boolean logginSession = false;

	/** The properties. */
	private Map<String, String> properties = new HashMap<String, String>();

	/** The entity manager factory. */
	private EntityManagerFactory entityManagerFactory;

	/** The entity manager. */
	private EntityManager entityManager;

	/**
	 * Update db properties.
	 */
	private void updateDBProperties() {

		// clear properties
		properties.clear();

		// Ensure RESOURCE_LOCAL transactions is used.
		properties.put(TRANSACTION_TYPE, PersistenceUnitTransactionType.RESOURCE_LOCAL.name());

		// Configure the internal EclipseLink connection pool
		properties.put(JDBC_DRIVER, DatabaseManager.DATABASE_DRIVERS.get(protocole));
		properties.put(JDBC_URL, getUrl());
		properties.put(JDBC_USER, userName);
		properties.put(JDBC_PASSWORD, password);

		// Configure logging. FINE ensures all SQL is shown
		properties.put(LOGGING_LEVEL, "FINE");
		properties.put(LOGGING_TIMESTAMP, Boolean.valueOf(loggingTimestamp).toString());
		properties.put(LOGGING_THREAD, Boolean.valueOf(loggingThread).toString());
		properties.put(LOGGING_SESSION, Boolean.valueOf(logginSession).toString());

		// Ensure that no server-platform is configured
		properties.put(TARGET_SERVER, TargetServer.None);

	}

	/**
	 * Login.
	 * 
	 * @throws Exception
	 *             the exception
	 */
	public boolean login() throws Exception {
		// update the properties
		updateDBProperties();

		// get the persistence manager
		try {
			entityManagerFactory = Persistence.createEntityManagerFactory(databaseName, properties);
			entityManager = entityManagerFactory.createEntityManager();

			System.out.println(entityManager);

			logger.fine(getUserName() + " : successfully connected on " + getUrl());

		} catch (Exception e) {
			logger.warning(userName + " : connection failed on " + getUrl());
			throw (e);
		}

		return isOpen();
	}

	public boolean isOpen() {
		if (entityManager != null)
			return entityManager.isOpen();
		else
			return false;
	}

	public String getUserRole() throws SQLException {

		// Create a native SQL request to get the role name
		// ONLY FOR POSTGRES
		// TODO: remove this hardcoded value !!
		Query query = entityManager
				.createNativeQuery("select distinct(role_name) from information_schema.applicable_roles");

		List<String> roles = query.getResultList();

		return roles.get(0);
	}

	/**
	 * Build the url (example : jdbc:postgresql://localhost:5432/superplanning)
	 * using the protocol, host, port and databaseName fields.
	 * 
	 * @return the url
	 */
	public String getUrl() {
		return "jdbc:" + protocole + "://" + host + ":" + port + "/" + databaseName;
	}

	/**
	 * Gets the protocole.
	 * 
	 * @return the protocole
	 */
	public String getProtocole() {
		return protocole;
	}

	/**
	 * Sets the protocole.
	 * 
	 * @param protocole
	 *            the new protocole
	 */
	public void setProtocole(String protocole) {
		this.protocole = protocole;
	}

	/**
	 * Gets the host.
	 * 
	 * @return the host
	 */
	public String getHost() {
		return host;
	}

	/**
	 * Sets the host.
	 * 
	 * @param host
	 *            the new host
	 */
	public void setHost(String host) {
		this.host = host;
	}

	/**
	 * Gets the port.
	 * 
	 * @return the port
	 */
	public int getPort() {
		return port;
	}

	/**
	 * Sets the port.
	 * 
	 * @param port
	 *            the new port
	 * @throws NegativeNumberException
	 *             the negative number exception
	 */
	public void setPort(int port) throws NegativeNumberException {
		if (port <= 0)
			throw new NegativeNumberException("The port cannot be negative");

		this.port = port;
	}

	/**
	 * Gets the database name.
	 * 
	 * @return the database name
	 */
	public String getDatabaseName() {
		return databaseName;
	}

	/**
	 * Sets the database name.
	 * 
	 * @param databaseName
	 *            the new database name
	 */
	public void setDatabaseName(String databaseName) {
		this.databaseName = databaseName;
	}

	/**
	 * Gets the user name.
	 * 
	 * @return the user name
	 */
	public String getUserName() {
		return userName;
	}

	/**
	 * Sets the user name.
	 * 
	 * @param userName
	 *            the new user name
	 */
	public void setUserName(String userName) {
		this.userName = userName;
	}

	/**
	 * Gets the password.
	 * 
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * Sets the password.
	 * 
	 * @param password
	 *            the new password
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Gets the logging level.
	 * 
	 * @return the logging level
	 */
	public String getLoggingLevel() {
		return loggingLevel;
	}

	/**
	 * Sets the logging level.
	 * 
	 * @param loggingLevel
	 *            the new logging level
	 */
	public void setLoggingLevel(String loggingLevel) {
		this.loggingLevel = loggingLevel;
	}

	/**
	 * Checks if is logging timestamp.
	 * 
	 * @return true, if is logging timestamp
	 */
	public boolean isLoggingTimestamp() {
		return loggingTimestamp;
	}

	/**
	 * Sets the logging timestamp.
	 * 
	 * @param loggingTimestamp
	 *            the new logging timestamp
	 */
	public void setLoggingTimestamp(boolean loggingTimestamp) {
		this.loggingTimestamp = loggingTimestamp;
	}

	/**
	 * Checks if is logging thread.
	 * 
	 * @return true, if is logging thread
	 */
	public boolean isLoggingThread() {
		return loggingThread;
	}

	/**
	 * Sets the logging thread.
	 * 
	 * @param loggingThread
	 *            the new logging thread
	 */
	public void setLoggingThread(boolean loggingThread) {
		this.loggingThread = loggingThread;
	}

	/**
	 * Checks if is loggin session.
	 * 
	 * @return true, if is loggin session
	 */
	public boolean isLogginSession() {
		return logginSession;
	}

	/**
	 * Sets the loggin session.
	 * 
	 * @param logginSession
	 *            the new loggin session
	 */
	public void setLogginSession(boolean logginSession) {
		this.logginSession = logginSession;
	}

	/**
	 * Set the properties.
	 * 
	 * @param properties
	 *            the properties
	 */
	public void setProperties(Map<String, String> properties) {
		this.properties = properties;
	}

	/**
	 * Gets the entity manager factory.
	 * 
	 * @return the entity manager factory
	 */
	public EntityManagerFactory getEntityManagerFactory() {
		return entityManagerFactory;
	}

	/**
	 * Gets the entity manager.
	 * 
	 * @return the entity manager
	 */
	public EntityManager getEntityManager() {
		return entityManager;
	}

	/**
	 * This method informs observers that an update of the model was performed.
	 */
	public void fireUpdate() {
		setChanged();
		notifyObservers(this);
	}
}